package com.ryan.notify.su;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;

import com.ryan.util.LogUtil;

public class SuCmd {
    public static String TAG = "SuperCommand";
    public static boolean isSuCmdLog = false;
    private static String OPERATION_ECHO="endofoperation";
    
    private static long READ_TIMEOUT = 5000;
    
	private static SuCmd sington;
	private Process suProcess;
	
	private ReentrantLock lock ;
	private SuCmd(){
	    lock = new ReentrantLock();
	}
	
	public static SuCmd getInstance(){
		if(sington == null){
			sington = new SuCmd();
		}
		return sington;
	}
	
	/**
	 * @param out
	 * @param value
	 * @throws IOException
	 */
	private void write(OutputStream out,String value) throws IOException{
	    lock.lock();
        try{
            try {
                FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
                    public String call()  { return null;}
                });futureTask.run();futureTask.get(5,TimeUnit.MILLISECONDS);
            } catch (Exception e) {}
            
    		out.write((value+"\n").getBytes());
    		if(isSuCmdLog)
    		    LogUtil.v(TAG, "write "+value);
        }finally{
            lock.unlock();
        }
	}
	
	/**
	 * @param out
	 * @throws IOException
	 */
	private void echo(OutputStream out) throws IOException{
	    lock.lock();
        try{
            try {
                FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
                    public String call()  { return null;}
                });futureTask.run();futureTask.get(5,TimeUnit.MILLISECONDS);
            } catch (Exception e) {}

            write(out,"echo "+OPERATION_ECHO);
        }finally{
            lock.unlock();
        }
	}
	
	/**
	 * @param in
	 * @return
	 * @throws IOException
	 * @throws SuFailException
	 * @throws TimeoutException 
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	private String read(final InputStream in) throws IOException, SuFailException, InterruptedException, ExecutionException, TimeoutException{
	    lock.lock();
        try{
            try {
                FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
                    public String call()  { return null;}
                });futureTask.run();futureTask.get(5,TimeUnit.MILLISECONDS);
            } catch (Exception e) {}
            
    	    if(in==null)
    	        return null;
    	    
    		FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
    
    			public String call() throws Exception {
    				ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
    				int read ;
    				while((read = in.read())!=-1){
    					byteArray.write(read);
    					int endIndex = byteArray.toString().indexOf(OPERATION_ECHO+"\n");
    					if(endIndex>=0){
    						return byteArray.toString().substring(0, endIndex);
    					}
    				}
    				throw new SuFailException();
    			}
    		});
    		futureTask.run();
    		return futureTask.get(READ_TIMEOUT, TimeUnit.MILLISECONDS);
        }finally{
            lock.unlock();
        }
	}
	
	/**
	 * @return
	 * true 
	 * false 
	 */
	public boolean retriveSu(){
	    lock.lock();
	    try{
    	    try {
                FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
                    public String call()  { return null;}
                });futureTask.run();futureTask.get(5,TimeUnit.MILLISECONDS);
            } catch (Exception e) {}
            
    		Process p = null;
    		try{
    			Runtime runtime = Runtime.getRuntime();
    			
    			if(suProcess==null){
    				p = runtime.exec("su");
    				if(isSuCmdLog)
    				    LogUtil.v(TAG,"ask for su");
    			}else{
    				p = this.suProcess;
    			}
    			InputStream in=p.getInputStream();
    			OutputStream out = p.getOutputStream();
    			echo(out);
    			String s = read(in);
    			if(isSuCmdLog)
    			    LogUtil.v(TAG, s +" result");
    			this.suProcess = p;
    			return true;
    		}catch(SuFailException suE){
    		    if(isSuCmdLog)
    		        LogUtil.e(TAG, "su fail",suE);
    			if(p!=null){
    				p.destroy();
    			}
    			return false;
    		}catch(Exception e){
    		    if(isSuCmdLog)
    		        LogUtil.e(TAG,"error",e);
    			if(p!=null){
    				p.destroy();
    			}
    			return false;
    		}
	    }finally{
	        lock.unlock();
	    }
	}
	
	
	public boolean execSuCmd(String[] commands) throws SuFailException{
		
	    lock.lock();
	    try{
    	    try {
                FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
                    public String call()  { return null;}
                });futureTask.run();futureTask.get(5,TimeUnit.MILLISECONDS);
            } catch (Exception e) {}
    	    
    		if(commands == null ){
    			return false;
    		}
    		if(retriveSu()){
    			try{
    				InputStream in = this.suProcess.getInputStream();
    				OutputStream out = this.suProcess.getOutputStream();
    				for (String cmd : commands) {
    					if(cmd!=null && !"".equals(cmd.trim()))
    						write(out, cmd.trim());
    				}
    				echo(out);
    				String result = read(in);
    				if(isSuCmdLog)
    				    LogUtil.v(TAG,"result: "+result);
    				return true;
    			}catch(IOException ioE){
    			    if(isSuCmdLog)
    			        LogUtil.v(TAG,"error" ,ioE);
    			} catch (InterruptedException e) {
    			    if(isSuCmdLog)
    			        LogUtil.v(TAG,"error" ,e);
    			} catch (ExecutionException e) {
    			    if(isSuCmdLog)
    			        LogUtil.v(TAG,"error" ,e);
    			} catch (TimeoutException e) {
    			    if(isSuCmdLog)
    			        LogUtil.v(TAG,"error" ,e);
    			}
    			return false;
    		}else{
    			throw new SuFailException();
    		}
	    }finally{
	        lock.unlock();
	    }
	}
}